'use strict';

require('sugar');
var secrets = require('secrets.js');
var crypto = require('crypto');
var fs = require('fs');
var path = require('path');

var masterPassword;
var saltLength;
var passwordLength;

function getDefaults(opts) {
  var defaults = {
    kekshare1: path.resolve(__dirname, './fixture/kekshare1'),
    kekshare2: path.resolve(__dirname, './fixture/kekshare2'),
    kekshare3: path.resolve(__dirname, './fixture/kekshare3'),
    keyfile: path.resolve(__dirname, './fixture/keyfile')
  };

  if (opts) {
    defaults = Object.merge(defaults, opts);
  }

  return defaults;
}

function generatePassword() {
  return crypto.randomBytes(passwordLength).toString('base64');
}

function generateSalt() {
  return crypto.randomBytes(saltLength).toString('base64');
}

function pbes2encryptSync(plainText, salt, password, encoding) {
  encoding = encoding || 'utf8';
  var derivedKey = crypto.pbkdf2Sync(password, salt, 5000, 32);
  var cipher = crypto.createCipheriv('aes256', derivedKey, '0000000000000000');
  var encryptedMessage = cipher.update(plainText, encoding, 'base64');
  return encryptedMessage + cipher.final('base64');
}

function pbes2decryptSync(encryptedMessage, salt, password, encoding) {
  encoding = encoding || 'utf8';
  var derivedKey = crypto.pbkdf2Sync(password, salt, 5000, 32);
  var decipher = crypto.createDecipheriv('aes256', derivedKey, '0000000000000000');
  var message = decipher.update(encryptedMessage, 'base64', encoding);
  return message + decipher.final(encoding);
}

function readKeyFile(keyfile) {
  var fd = fs.openSync(keyfile, 'r');
  var fsize = fs.statSync(keyfile).size;
  var buf = new Buffer(fsize);
  fs.readSync(fd, buf, 0, buf.length, 0);
  fs.closeSync(fd);
  return {
    salt: buf.slice(0, saltLength),
    encryptedPassword: buf.slice(saltLength)
  };
}

function writeKeyFile(keyfile, salt, encryptedPassword) {
  var fd = fs.openSync(keyfile, 'w');
  var buf1 = new Buffer(salt, 'base64');
  var buf2 = new Buffer(encryptedPassword, 'base64');
  var buf = Buffer.concat([buf1, buf2]);
  fs.writeSync(fd, buf, 0, buf.length, 0);
  fs.closeSync(fd);
}

function encrypt(plainText, keyfile) {
  if (!keyfile) {
    keyfile = getDefaults().keyfile;
  }

  var password, salt, encryptedPassword;
  if (!fs.existsSync(keyfile)) {
    password = generatePassword();
    salt = generateSalt();
    encryptedPassword = pbes2encryptSync(password, salt, masterPassword, 'base64');
    writeKeyFile(keyfile, salt, encryptedPassword);
  } else {
    var keyfileObject = readKeyFile(keyfile);
    encryptedPassword = keyfileObject.encryptedPassword.toString('base64');
    salt = keyfileObject.salt.toString('base64');
    password = pbes2decryptSync(encryptedPassword, salt, masterPassword, 'base64');
  }
  return pbes2encryptSync(plainText, salt, password);
}

function decrypt(encryptedMessage, keyfile) {
  if (!keyfile) {
    keyfile = getDefaults().keyfile;
  }

  if (keyfile && fs.existsSync(keyfile)) {
    var keyfileObject = readKeyFile(keyfile);
    var encryptedPassword = keyfileObject.encryptedPassword;
    var salt = keyfileObject.salt;
    var password = pbes2decryptSync(encryptedPassword.toString('base64'), salt.toString('base64'), masterPassword, 'base64');
    return pbes2decryptSync(encryptedMessage, salt.toString('base64'), password);
  } else {
    throw new Error('keyfile not defined or keyfile does not exists');
  }
}


function decrypt2(encryptedMessageWithEnc, keyfile) {
  if (!keyfile) {
    keyfile = getDefaults().keyfile;
  }

  var password = encryptedMessageWithEnc;
  if (encryptedMessageWithEnc !== undefined && encryptedMessageWithEnc.startsWith('enc(', 0, false) && encryptedMessageWithEnc.endsWith(')')) {
    password = decrypt(encryptedMessageWithEnc.slice(4, -1), keyfile);
  }
  return password;
}

function reconstructMasterPassword(opts) {
  var share1Path = opts.kekshare1;
  var share2Path = opts.kekshare2;
  var share3Path = opts.kekshare3;

  var share1, share2, share3;
  if ((share1Path) && (fs.existsSync(share1Path))) {
    share1 = fs.readFileSync(share1Path, {
      encoding: 'utf-8'
    });
  }
  if ((share2Path) && (fs.existsSync(share2Path))) {
    share2 = fs.readFileSync(share2Path, {
      encoding: 'utf-8'
    });
  }
  if ((share3Path) && (fs.existsSync(share3Path))) {
    share3 = fs.readFileSync(share3Path, {
      encoding: 'utf-8'
    });
  }
  var comb;
  if (share1 && share2 && share3) {
    comb = secrets.combine([share1, share2, share3]);
  } else if (share1 && share2) {
    comb = secrets.combine([share1, share2]);
  } else if (share1 && share3) {
    comb = secrets.combine([share1, share3]);
  } else if (share2 && share3) {
    comb = secrets.combine([share2, share3]);
  } else {
    throw new Error('Could not read at least 2 shares.');
  }
  comb = secrets.hex2str(comb);
  return comb.toString('base64');
}

function splitPwd(key, filePrefix) {
  var hexKey = secrets.str2hex(key);
  var shares = secrets.share(hexKey, 3, 2);
  if (filePrefix) {
    shares.each(function (share, index) {
      var fileSuffix = index + 1;
      var fileName = filePrefix + fileSuffix;
      fs.writeFileSync(fileName, share, {
        flag: 'wx'
      });
    });
  }
  return shares;
}

module.exports = exports = function (opts) {
  opts = getDefaults(opts);

  masterPassword = reconstructMasterPassword(opts);
  saltLength = opts.saltLength || 128;
  passwordLength = opts.passwordLength || 64;

  return {
    generatePassword: generatePassword,
    generateSalt: generateSalt,
    encrypt: encrypt,
    decrypt: decrypt,
    decrypt2: decrypt2,
    splitPwd: splitPwd
  };
};